import dagre from "@dagrejs/dagre";
import Raphael from 'raphael';
import svgPanZoom from 'svg-pan-zoom';
import { useWindowSize } from "@vueuse/core";
import { linkConnector } from './link';
import configuration from './configuration';

export class BruceMindMap {
    constructor(
        el = '', 
        options = {
            rankdir: 'TB',
            align: undefined, 
            nodesep: 50,
            edgesep: 10,
            ranksep: 50,
            marginx: 0,
            marginy: 0,
            ranker: 'network-simplex',
            directed: true,
            multigraph: false,
            compound: false,
            nodes: [],
            links: [],
            edge:{
                fill: 'tomato',
                connector: 'curve'
            },
            viewPort: {
                zoom: 0.6
            }
        }
    ) {
        this.el = el
        this.config = Object.assign(configuration, options)
        this.graph = this.graph = new dagre.graphlib.Graph({ 
            directed: this.config.directed, 
            multigraph: this.config.multigraph, 
            compound: this.config.compound 
        })
        this.paper = null
        this.canvas = {}
        this.panZoomTiger = {}
        this.initCanvas()
        this.initGraph()
        this.initRaphael()
        this.initSvgPanZoom()
    }

    // 绘制画布
    initCanvas () {
        try {
            this.canvas = typeof this.el === 'string' ? document.querySelector(`${this.el}`) : this.el
        } catch (error) {
            throw new Error(`The container id${ this.el } is invalid or the page does not have a corresponding id container.`)
        }
    }
    
    // 初始化图形
    initGraph () {
        const { clientWidth, clientHeight } = this.canvas;
        // set Graph
        this.graph.setGraph({
            rankdir: this.config.rankdir,
            width: clientWidth,
            height: clientHeight,
            align: this.config.align,
            nodesep: this.config.nodesep,
            edgesep: this.config.edgesep, 
            ranksep: this.config.ranksep,
            marginx: this.config.marginx,
            marginy: this.config.marginy,
            ranker: this.config.ranker
        })
        // 1 默认为每个新边分配一个新对象作为标签。
        this.graph.setDefaultEdgeLabel(function() { return {}})
        // 2 添加节点到图形容器，第一个参数是节点ID，第二个有关节点的元数据，此处为每个节点添加标签
        this.config.nodes.forEach(item => this.graph.setNode(item.id, item.node))
        // 3 给图形容器添加边
        this.config.links.forEach(edge => this.graph.setEdge(edge.sourceId, edge.targetId))
    }
    // 渲染数据到页面
    initRaphael () {
        this.paper = null;
        const { clientWidth, clientHeight } = this.canvas;    
        this.updateLayout()
        this.paper = new Raphael(this.canvas, clientWidth, clientHeight)
        this.updateRender()
        // console.log(this.graph, this.getNodes(), clientWidth, clientHeight);
    }
    // 给svg绑定预览辅助工具
    initSvgPanZoom() {
        const svgElement = this.canvas.querySelector(`${this.el} > svg`)
        this.panZoomTiger = svgPanZoom(svgElement, {
            viewportSelector: `${this.el} > svg`,
            panEnabled: true, // 启用拖拽
            zoomEnabled: true, // 开启缩放
            controlIconsEnabled: true, // 开启图标控制，点击图标缩放
            dblClickZoomEnabled: false, // 禁止双击鼠标进行缩放视图
            mouseWheelZoomEnabled: true, // 开启鼠标滚轮缩放视图
            zoomScaleSensitivity: 0.2, // 变焦灵敏度 (Default 0.2)
            minZoom: 0.5, // 最小缩放级别 (Default 0.5)
            maxZoom: 10, // 最大缩放级别  (Default 10)
            fit: true, // 启用或禁用 SVG 中的视口适配
            center: true, // 在 SVG 中启用或禁用视口居中
            refreshRate: 10
        })
        // 默认以1倍查看视图，可以放大当前视图倍数，以获得更加的视觉效果
        this.panZoomTiger.zoom(this.config.viewPort.zoom)
        // 配置最小倍数
        // console.log(this.viewPort, 'initSvgPanZoom');
    }

   // 重新设置svg的视图属性，让内容居中显示
    resetViewBox = () => {
        const { clientWidth, clientHeight } = this.canvas; 
        if (this.graph) {
            // DagreJS图形的宽度和高度
            const graphWidth = this.graph.graph().width;
            const graphHeight = this.graph.graph().height;

            // 计算位移量
            const dx = (clientWidth - graphWidth) / 2;
            const dy = (clientHeight - graphHeight) / 2;

            // 设置viewBox保证内容完全展示
            this.paper.setViewBox(-dx, -dy, clientWidth, clientHeight)
        }
    }

    // 更新渲染节点和边到paper中
    updateRender () {
        const nodeList = this.getNodes(), linkList = this.getEdges();
        let i = 0
        while (i < nodeList.length) {
            // const rect = paper.rect(nodes[i].x, nodes[i].y, nodes[i].width, nodes[i].height, 5)
            // Paint布局（例如SVG和HTML）通常使用标准的盒子模型，元素的x和y位置从左上角开始计算。
            // 然而，DagreJS对位置的计算则略有不同，其x和y是基于元素的中心点进行的。
            // 需要转换元素x和y坐标才能对齐连线
            const rect = this.paper.rect(nodeList[i].x - nodeList[i].width / 2, nodeList[i].y - nodeList[i].height / 2, nodeList[i].width, nodeList[i].height, 5);
            const rectBox = rect.getBBox()
            rect.attr({ stroke: Raphael.getColor(), 'stroke-width': 1 })
            const { cx, cy, x:Rx, y:Ry, width, height }  = rectBox
            let textX = Rx + width / 2, textY = Ry + height / 2;
            const text = this.paper.text(textX, textY, nodeList[i].label)
            text.attr({ 'font-size': 18, 'font-weight': '600' })
            i++;
        }

        // 获取连接线数据
        const newLinks = linkConnector(this.config.edge.connector, linkList)
        let j = 0;
        while (j < newLinks.length) {
            const path = this.paper.path(newLinks[j])
                path.attr({ stroke: this.config.edge.fill, 'stroke-width': 3 })
            j++;
        }

        // svg内容太多时会发生遮挡，影响svg-pan-zoom插件内容居中属性
        // this.resetViewBox()
    }

    // 重新计算布局坐标，如果切换布局方式
    updateLayout () {
        // 此处不能这样配置，这样配置会覆盖默认值，需要依据this.config参数进行
        // this.graph.setGraph({rankdir: this.config.rankdir })
        // 让dagre为这些节点和边做布局
        // layout图形将使用布局信息进行更新，节点node获得节点中心坐标x和y
        // 边edge获取一个points属性，该属性包括边的控制点坐标以及边与节点相交处的点
        // 边edge中此弯曲中心的x坐标和y坐标
        dagre.layout(this.graph)
    }
    getNodes () {
        let coordinateNodes = [], this_ = this;
        this.graph.nodes().forEach(function(v) {
            coordinateNodes.push(this_.graph.node(v))
            // console.log("Node " + v + ": " + JSON.stringify(this_.graph.node(v)));
        });
        return coordinateNodes
    }
    getEdges () {
        let coordinateEdges = [], this_ = this;
        this.graph.edges().forEach(function(e) {
            coordinateEdges.push(this_.graph.edge(e))
            // console.log("Edge " + e.v + " -> " + e.w + ": " + JSON.stringify(this_.graph.edge(e)));
        });
        return coordinateEdges
    }

    destroy () {
        this.panZoomTiger.destroy()
        this.paper.clear()
        this.paper.remove()
        this.paper = null
    }
}